/**
 * NodeRetrieval.java
 * ************************** 
 * @date May 30, 2011
 * @author Michele Tamburini
 * 
 * **************************
 */
package infrastructureAPI.algorithms.instanceTools;

import gossipServices.basic.nodeDescriptors.NodeDescriptor;
import gossipServices.pss.PeerSamplingServiceAPI;
import infrastructureAPI.ConcreteRequester;
import infrastructureAPI.Requester;
import infrastructureAPI.ResponseException;
import infrastructureAPI.APIMessages.RequestMessage;
import infrastructureAPI.APIMessages.ResponseMessage;
import infrastructureAPI.algorithms.GeneralAPIFunctionAlgorithm;
import infrastructureAPI.algorithms.instanceTools.facilities.TManVerifier;
import infrastructureAPI.backendGate.APIRoutineFunctions;

import java.net.UnknownHostException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Vector;

import networkRmi.NetworkManager;

/**
 * Description:
 * The NodeRetrieval abstract class extends the GeneralAPIFunctionAlgorithm
 * class. It may be a good base class for those algorithms that need
 * to pickup some peer from the net.
 * It offers the retrieveNodesFromPeerSamplingService() algorithm 
 * that select some nodes from the PeerSamplingService and
 * provide them as a List.  
 *
 */
public abstract class NodeRetrieval extends GeneralAPIFunctionAlgorithm {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	protected String registeredService;
	
	private Requester retrievalRequester;
	private List<NodeDescriptor> contactedNodes;
	
	protected NodeRetrieval(){}
	
	public NodeRetrieval(Requester requester){
		super(requester);
	}
	
	/**
	 * Algorithm for the retrieval of some peers from the PeerSamplingService.
	 * It returns a List<NodeDescriptor> of running nodes from the 
	 * PeerSamplingService. Each returned node is guaranteed to be up and running.
	 * Nodes are selected through the selectPeer():NodeDescriptor
	 * method of the PeerSamplingService with the settings previously defined 
	 * (in particular the random or tail selection).
	 * The input parameter vec:List<NodeDescriptor> enables the caller
	 * to specify a list of NodeDescriptor(s) used as initializer.
	 * The returned List is ensured to contains the whole previous 'vec'
	 * plus the new picked-up entries. 
	 * The function operates directly into 'vec', please be sure to 
	 * call this method in a thread-safe condition. 
	 * The new inserted nodes are fresh, so not belonging to 'vec'.
	 * The algorithm stops only when 'numberOfNodes' new nodes are
	 * correctly selected from the PeerSamplingService.
	 * So the caller has to ensure that the total size
	 * ('vec.size()' + 'numberOfNodes') 
	 * doesn't exceeds the actual available pool of running peers.
	 * This method may incur in a blocking situation if the 'numberOfNodes'
	 * cannot be reached (it depends from the modification of the PSS View).
	 * Actually the retrieval of fresh nodes is actually used to add 
	 * machines to an overlay.
	 * For this reason the retrieveNodesFromPeerSamplingService function
	 * verify if an instance of T-Man is running in each extracted node from the PSS.
	 * If not then the node can be selected to become part of the overlay.
	 * 
	 * @param nm : NetworkManager
	 * @param pss : PeerSamplingServiceAPI
	 * @param numberOfNodes : int
	 * @param localNode : NodeDescriptor
	 * @param vec : List<NodeDescriptor>
	 * @return vec : List<NodeDescriptor>
	 */
	public List<NodeDescriptor> retrieveNodesFromPeerSamplingService(NetworkManager nm,
			PeerSamplingServiceAPI pss, int numberOfNodes, NodeDescriptor localNode,
			List<NodeDescriptor> vec){
		NodeDescriptor pickedUpPeer = localNode;
//		pickedUpPeer = localNode;
		boolean extractAgain = true; 
//			vec.contains(pickedUpPeer);
		
		contactedNodes = new Vector<NodeDescriptor>();
		registeredService = NodeRetrieval.class.getSimpleName()+"_"+ localNode.getNodeName();
		retrievalRequester = new ConcreteRequester(localNode.getNodeAddress(), 
				registeredService, APIRoutineFunctions.RMIREGISTRY_STADARD_PORT);
		
		//@FOR DEBUG
//		System.out.println(this.getClass().getSimpleName()+" retrieve from PSS with " +
//				"starting nodes: "+vec);

		// Initialization of the ResponseReceiver
		try {
			APIRoutineFunctions.initializeResponseReceiver(localNode.getNodeAddress(), registeredService);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
		
		
		// TODO
		// REQUIRES optimization if the numberOfNode cannot be reached....
		/*
		 * Our first implementation is an optimistic strategy in which only the number
		 * of required nodes is contacted.
		 */
		int correctlyRetrieved = 0;
		while(correctlyRetrieved<numberOfNodes){
			while(extractAgain){
				// 1) select a new peer
				pickedUpPeer = pss.getPeer();
				extractAgain = false;
				
				// 2) verify if not just inserted in the building vector
				extractAgain |= contactedNodes.contains(pickedUpPeer);
				
				// 3) verify if it is really reliable with a ping
				if(extractAgain == false){
					// because this is a remote invocation we want to perform it only
					// when necessary
					try {
						// ping returns true if the node is reliable
						extractAgain |= !(nm.ping(pickedUpPeer.getNodeAddress(), pickedUpPeer.getNodeName()));
					} catch (RemoteException e) {
						extractAgain = true;
					} catch (NotBoundException e) {
						extractAgain = true;
					} 
				}
				
			}
			
			contactPickedNode(nm,localNode, pickedUpPeer);
			boolean ableToAdd = waitForTheAnswer();
			if(ableToAdd){
				vec.add(pickedUpPeer);
				correctlyRetrieved++;
			}
			
			
			contactedNodes.add(pickedUpPeer);
			
			//@FOR DEBUG
			System.out.println(this.getClass().getSimpleName()+" selected: "+pickedUpPeer);
			extractAgain = true;
		}
		
		
//		System.out.println(Thread.currentThread().getName()+" is waiting for answers");
//		waitForAnswers(vec);
		
		APIRoutineFunctions.disconnectReceiver();
		
		//@FOR DEBUG
		System.out.println(this.getClass().getSimpleName()+" resulting view: \n"+vec);
//		vec.add(pickedUpPeer);
		
		return vec;
	}

	/**
	 * @return
	 */
	private boolean waitForTheAnswer() {
		boolean able = false;
		ResponseMessage response = APIRoutineFunctions.receiveResponse();
		try {
			response.getAPIResponse().getResponse();
		} catch (ResponseException e) {
//			e.printStackTrace();
			/*
			 * If a NotFoundService exception occurs we can add
			 * the sender node into the output vector
			 */
			NodeDescriptor targetNode = response.getSender();
			//@FOR DEBUG
			System.out.println(this.getClass().getSimpleName()+" " +
					targetNode + " answered to the retrieval");
			able = true;
//			vec.add(targetNode);
//			contactedNodes.remove(contactedNodes.indexOf(targetNode));
		}
		return able;
	}


	/**
	 * @param pickedUpPeer
	 */
	private void contactPickedNode(NetworkManager nm,
			NodeDescriptor localNode, NodeDescriptor pickedUpPeer) {
		RequestMessage reqMsg = new TManVerifier(retrievalRequester).createRequestMessage(
				pickedUpPeer.getNodeAddress(), pickedUpPeer.getNodeName());
		try {
			nm.send(reqMsg, pickedUpPeer.getNodeAddress());
		} catch (RemoteException e) {
			e.printStackTrace();
		} catch (NotBoundException e) {
			e.printStackTrace();
		}
	}

}
